iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Software Development

Kotlin on the way系列 第 27

Day 27 設計模式 裝飾和代理的細節 Proxy pattern and Decorator pattern Structural

  • 分享至 

  • xImage
  •  
  • proxy
  • decorator
  • mix proxy and decorator
  • summary

代理 Proxy

代理的觀念非常的生活化,像是日本代購、掏寶集運等等,都是委託另一個人幫忙做事,而客戶端並不在意事情是如何結束的,那以更工程師的範例來看

可以分成基層工程師,負責的工作是寫程式碼
另一個是工程師主管,負責的工作是管工程師進度

interface Engineer {
    fun deliveryProduct()
}

class AndroidEngineer:Engineer {
    override fun deliveryProduct(){
        // build android application
    }
}

class ProxyEngineer:Engineer {
    private val androidEngineer = AndroidEngineer()
    override fun deliveryProduct(){
        // build android application
        androidEngineer.deliveryProduct()
    }
}

fun main(){
    val engineerLeader = ProxyEngineer()
    engineerLeader.deliveryProduct()
}

那在情境中,PM 是直接跟工程師主管對接,談定要交付什麼功能、時限等等,而工程師主管就會有交付函示,在主管的職責下,他會將其中的功能交給底下的工程師執行,去做真正寫程式碼的事情,而 PM 對此完全不需了解,只要知道主管能夠交付產品就可以了

裝飾 Decorator

裝飾模式在功能的擴展上非常的有用,透過依賴抽象和實作抽象,達到動態的增刪功能,在解耦的部分個人認為做得很好,那他的依賴抽象和實作抽象,具體來說做了些什麼呢

   val dos = DataOutputStream(
       BufferedOutputStream(
           FileOutputStream("data.txt")
       )
   ) 
    val d: Byte = 3
    val i = 14
    val ch = 'c'
    val f = 3.3f

    dos.use {
        it.writeByte(d.toInt())
        it.writeInt(i)
        it.writeChar(ch.code)
        it.writeFloat(f)
    }

    val dis = FileInputStream("data.txt")
        .let(::BufferedInputStream)//緩存
        .let(::DataInputStream)//數據類型

    dis.use {
        println(it.readByte())
        println(it.readInt())
        println(it.readChar())
        println(it.readFloat())
    }

在 Java 的 io stream 就以裝飾器模式而出名,透過這種設計模式,我們可以動態的增加原本 FileStream 的職責,透過 BufferStream為其加入緩存,DataStream 為其加入數據類型

在這以上,以 Kotlin 編寫的話,可以用兩種方式完成裝飾模式,一種是 11~15 的,多層級的物件建構式,而另一種是搭配 scope function 在 28~30 行,使用 let 和 function reference,使其更簡短,這前面可讀性有講過

mix proxy and decorator

大家應個有發現,代理和裝飾模式很像,連依賴圖都非常相似,沒錯,這兩者最大的差別在於
代理

  • 調用方操作代理,不需知道被代理的對象
  • 可代理的實體是確定的

裝飾

  • 可裝飾的類別不固定
  • 動態組合
  • 每個裝飾類負責一個功能

當然,設計模式並非一成不變,我們可以視情況組合不同的設計模式,下面我們將利用代理和裝飾模式,完成一個去快取功能的 api 請求

data class Request(
    val endPoint: String
)

data class Response(
    val result: String
)

class Logger {
    fun printLog(message: String) {
        println("print log from" + message)
    }
}

class Cache {
    private val cacheMap = mutableMapOf<Request, Response>()
    fun put(request: Request, response: Response) {
        cacheMap[request] = response
    }

    fun get(request: Request): Response? {
        return cacheMap[request]
    }
}

interface Processor {
    fun process(res: Request): Response
}

class RequestProcessor : Processor {
    override fun process(res: Request): Response {
        return Response("result from ${res.endPoint}")
    }
}

class LoggerProcessor(
    val logger: Logger,
    val processor: Processor
) : Processor {
    override fun process(res: Request): Response {
        logger.printLog(res.endPoint)
        return processor.process(res)
    }
}

class CacheProcessor(
    val cache: Cache,
    val processor: Processor
) : Processor {
    override fun process(res: Request): Response {
        return cache.get(res) ?: run {
            val result = processor.process(res)
            cache.put(res, result)
            result
        }
    }
}

fun main() {
    val request = Request("http://example.com/")
//    val processor:Processor = RequestProcessor()

//    val processor:Processor = CacheProcessor(
//        Cache(), RequestProcessor()
//    )

//    val processor:Processor = LoggerProcessor(
//        Logger(),RequestProcessor()
//    )

    val processor: Processor = CacheProcessor(
        Cache(),
        LoggerProcessor(
            Logger(),
            RequestProcessor()
        )
    )
    println(processor.process(request))
}

可以看到,在快取物件中,該物件可以能改變行為,屬於代理模式,而 Logger 物件中,不會改變行為,屬於裝飾模式,那在這種範例下,順序就會變得重要,如果快取在最外層,裡面的就不一定會執行到

summary

從設計模式的角度看,裝飾模式,總是會將程式向下執行,而代理模式,會實際的改變行為,像是快取的功能可能會向下執行也可能不會,但我們也能將代理放入裝飾裡面使用

English

  • proxy
  • decorator
  • mix proxy and decorator
  • summary

Proxy

The concept of proxy is close to our daily life, like purchasing agent, we intrust agent to do something for us, and we as client don't care about how things been done, but we can take a sample in engineer side

The engineer in charge of writing code
Engineer manager in charge of schedule of project

interface Engineer {
    fun deliveryProduct()
}

class AndroidEngineer:Engineer {
    override fun deliveryProduct(){
        // build android application
    }
}

class ProxyEngineer:Engineer {
    private val androidEngineer = AndroidEngineer()
    override fun deliveryProduct(){
        // build android application
        androidEngineer.deliveryProduct()
    }
}

fun main(){
    val engineerLeader = ProxyEngineer()
    engineerLeader.deliveryProduct()
}

In the situation, project manager will communicate with manager, discuss about demand and deliver deadline, then the engineer manager class define its deliver function

As the responsibility of the manager, he will request the engineer team to start those task, and the project manager doesn't need to know about the coding part, pm only need to know that the engineer manager will make sure the deliver properly.

Decorator

Decorator will be very useful when extending the function, by relying on abstract and implementing abstract, dynamic add and delete function, work well in decouple, but what does the part of abstract in decorator in action?

   val dos = DataOutputStream(
       BufferedOutputStream(
           FileOutputStream("data.txt")
       )
   ) 
    val d: Byte = 3
    val i = 14
    val ch = 'c'
    val f = 3.3f

    dos.use {
        it.writeByte(d.toInt())
        it.writeInt(i)
        it.writeChar(ch.code)
        it.writeFloat(f)
    }

    val dis = FileInputStream("data.txt")
        .let(::BufferedInputStream)//緩存
        .let(::DataInputStream)//數據類型

    dis.use {
        println(it.readByte())
        println(it.readInt())
        println(it.readChar())
        println(it.readFloat())
    }

Java.io.stream is famous by its decorator, by using this design pattern, we can dynamically add the responsibility to FileStream, add buffer with BufferStream, and data type with DataStream

With all those knowledge above, we have two way to implement decorator pattern in Kotlin, first one is in line 11~15, the other one in line 28~30, using let and function reference

mix proxy and decorator

Proxy and decorator is similar in practice and the dependency graph, the different between them is

Proxy

  • client operator agent, doesn't know about the instance
  • not dynamic

Decorator

  • dynamic
  • each decorate class in charge of one function

Of course, design pattern is not always same, we can composite different design pattern by situation, the following example will demonstrate Api request with cache

data class Request(
    val endPoint: String
)

data class Response(
    val result: String
)

class Logger {
    fun printLog(message: String) {
        println("print log from" + message)
    }
}

class Cache {
    private val cacheMap = mutableMapOf<Request, Response>()
    fun put(request: Request, response: Response) {
        cacheMap[request] = response
    }

    fun get(request: Request): Response? {
        return cacheMap[request]
    }
}

interface Processor {
    fun process(res: Request): Response
}

class RequestProcessor : Processor {
    override fun process(res: Request): Response {
        return Response("result from ${res.endPoint}")
    }
}

class LoggerProcessor(
    val logger: Logger,
    val processor: Processor
) : Processor {
    override fun process(res: Request): Response {
        logger.printLog(res.endPoint)
        return processor.process(res)
    }
}

class CacheProcessor(
    val cache: Cache,
    val processor: Processor
) : Processor {
    override fun process(res: Request): Response {
        return cache.get(res) ?: run {
            val result = processor.process(res)
            cache.put(res, result)
            result
        }
    }
}

fun main() {
    val request = Request("http://example.com/")
//    val processor:Processor = RequestProcessor()

//    val processor:Processor = CacheProcessor(
//        Cache(), RequestProcessor()
//    )

//    val processor:Processor = LoggerProcessor(
//        Logger(),RequestProcessor()
//    )

    val processor: Processor = CacheProcessor(
        Cache(),
        LoggerProcessor(
            Logger(),
            RequestProcessor()
        )
    )
    println(processor.process(request))
}

as you can see, in the cache object, it can actually change behavior, belongs to proxy pattern, in Logger object, it wouldn't change behavior, belongs to decorator pattern, in situation like this, the order is matter

summary

Decorator pattern always execute program, and proxy pattern will change the behavior, and we can combine different pattern


上一篇
Day 26 設計模式 工廠建構的細節
下一篇
Day 28 設計模式 狀態模式和狀態機
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言